/// axum是一个专注于人机工程学和模块化的web应用程序框架。
/*
1、高级功能
    将请求路由到具有无宏API的处理程序。
    使用提取器声明性地解析请求。
    简单且可预测的错误处理模型。
    用最少的样板生成响应。
    充分利用由中间件、服务和实用程序组成的tower和tower http生态系统。
特别是最后一点是axum与其他框架的区别。axum没有自己的中间件系统，而是使用tower:：Service。这意味着axum可以免费获得超时、跟踪、压缩、授权等。它还使您能够与使用hyper或tonic编写的应用程序共享中间件。
2、高性能
axum是hyper之上相对较薄的一层，增加的开销很小。因此，axum的性能可与hyper相媲美。你可以在这里和这里找到基准测试。
3、安全
axum禁止使用 unsafe 不安全代码以确保所有内容都在100%安全的Rust中实现。
*/

use axum::{body::Bytes, extract::{Path, State}, http::{header, HeaderValue, StatusCode}, response::IntoResponse, routing::{get, post}, Router, BoxError};
use std::{
    collections::HashMap,
    net::{Ipv4Addr, SocketAddr},
    sync::{Arc, RwLock},
    time::Duration,
};
use std::sync::Mutex;
use axum::error_handling::HandleErrorLayer;
use axum::http::{Method, Uri};
use sea_orm::DatabaseConnection;
use tokio::net::TcpListener;
use tower::{Layer, ServiceBuilder};
use tower_http::{
    cors::{Any, CorsLayer},
    timeout::TimeoutLayer,
    trace::{DefaultMakeSpan, DefaultOnResponse, TraceLayer},
    LatencyUnit, ServiceBuilderExt,
};
use rust_axum::{create_user, html, json, path, query, request, root};


#[tokio::main]
async fn main() -> Result<(), anyhow::Error>{
    // 初始化 tracing 日志链路追踪
    tracing_subscriber::fmt::init();

    // 创建一个 IPv4 格式的服务地址，并在端口 3000 上进行全局监听
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    tracing::debug!("listening on {:?}", listener);
    // 使用 hyper 运行我们的 HTTP 服务器，并绑定上面的监听和路由
    axum::serve(
        listener,
        app().into_make_service()
    ).await.unwrap();

    Ok(())
}

// 在 Rust 中，尤其是在构建 Web 服务或网络服务时，AppState 结构体经常被用作一个容器，用来存储和管理应用的全局状态或资源。
// 这些资源或状态可以是数据库连接、缓存实例、配置选项、身份验证信息、日志记录器或其他在整个应用中共享或频繁使用的对象。
#[derive(Clone, Debug)]
pub struct AppState {
    db: Arc<RwLock<HashMap<String, Bytes>>>,
    // 关于 Arc 和 Mutex 的使用，可以参考： https://rustwiki.org/zh-CN/book/ch16-00-concurrency.html
    // mysql: Arc<Mutex<DatabaseConnection>>,
}

/// 配置参考： https://github.com/tower-rs/tower-http/blob/main/examples/axum-key-value-store/src/main.rs
// 定制化路由
fn app() -> Router {
    // 构建我们的数据库来保存键/值对
    let state = AppState {
        db: Arc::new(RwLock::new(HashMap::new())),
    };

    // 定义敏感的请求头
    let sensitive_headers: Arc<[_]> = vec![header::AUTHORIZATION, header::COOKIE].into();

    /// axum 中间件使用官方文档： https://docs.rs/axum/latest/axum/middleware/index.html
    // 使用 tower 构建我们的应用服务中间件堆栈，类似于过滤器链。此处的中间件可以理解为为了处理某一问题的过滤器。
    // Java 里的中间件一般是一个依赖包但他的整体最总目的也是为了处理某一问题，相当于是一个大型的过滤器了
    let middleware = ServiceBuilder::new()
        // 将 “Authorization” 和 “Cookie” 标头标记为敏感，这样它就不会显示在日志中
        .sensitive_request_headers(sensitive_headers.clone())
        // 为所有请求添加高级跟踪/日志记录
        .layer(
            TraceLayer::new_for_http()
                .on_body_chunk(|chunk: &Bytes, latency: Duration, _: &tracing::Span| {
                    tracing::trace!(size_bytes = chunk.len(), latency = ?latency, "sending body chunk")
                })
                .make_span_with(DefaultMakeSpan::new().include_headers(true))
                .on_response(DefaultOnResponse::new().include_headers(true).latency_unit(LatencyUnit::Micros)),
        )
        // 为所有请求添加跨域
        .layer(
            CorsLayer::new()
                // 所有方法都可以允许
                .allow_methods(Any)
                .allow_origin(Any)
                .allow_headers(Any)
        )
        // 为所有响应添加敏感标头
        .sensitive_response_headers(sensitive_headers)
        // 设置服务请求到响应超时时长
        .layer(TimeoutLayer::new(Duration::from_secs(10)))
        // 压缩响应
        .compression()
        // 如果还没有“内容类型”，请设置一个。
        .insert_response_header_if_not_present(
            header::CONTENT_TYPE,
            HeaderValue::from_static("application/octet-stream"),
        )
        // 自定义的错误处理的中间件,此处的错误处理函数是处理请求超时错误
        .layer(HandleErrorLayer::new(handle_timeout_error))
        // 设置服务超时时长
        .timeout(Duration::from_secs(10));

    // 为我们的应用程序构建一个路由
    // 路由的定义可以参考： https://docs.rs/axum/latest/axum/struct.Router.html#method.nest_service
    Router::new()
        // `GET /` 转到 `root`
        .route("/", get(root))
        .route("/html", get(html))
        // `POST /users` 转到 `create_user`
        .route("/users", post(create_user))
        // Axum 提供了一些内置的提取器，可以方便地从请求中提取数据。以下是一些示例路由：
        .route("/path/:user_id", get(path))
        .route("/path/:user_id/items", get(path))
        .route("/query", post(query))
        .route("/json", post(json))
        .route("/request", post(request))
        // 为路由添加中间层配置
        .layer(middleware)
        // 为路由配置全局共享资源
        .with_state(state)
}

// 通过将错误转换为实现了 IntoResponse 的东西来处理错误，只有实现了 IntoResponse 的东西才能被用作响应
// 基础的数据类型默认都实现了 IntoResponse，因此我们可以直接返回它们。结构体、枚举、需要实现 IntoResponse。
async fn handle_not_found() -> impl IntoResponse {
    (StatusCode::NOT_FOUND, "Page not found")
}

// 自定义的错误处理函数，处理 anyhow::Error 错误
async fn handle_anyhow_error(err: anyhow::Error) -> (StatusCode, String) {
    if err.is::<tower::timeout::error::Elapsed>() {
        (
            StatusCode::REQUEST_TIMEOUT,
            "Request took too long".to_string(),
        )
    } else {
        (
            StatusCode::INTERNAL_SERVER_ERROR,
            format!("Something went wrong: {err}"),
        )
    }
}

// 自定义的错误处理函数，处理请求超时错误
async fn handle_timeout_error (
    // Method 和 Uri 是提取器，因此可以在这里使用
    method: Method,
    uri: Uri,
    // 最后一个参数必须是错误本身
    err: BoxError,
) -> (StatusCode, String) {
    if err.is::<tower::timeout::error::Elapsed>() {
        (
            StatusCode::REQUEST_TIMEOUT,
            "Request took too long".to_string(),
        )
    } else {
        (
            StatusCode::INTERNAL_SERVER_ERROR,
            format!("`{method} {uri}` failed with {err}"),
        )
    }
}